﻿using System;
using BrettM.Security;

namespace BrettM.Commands
{
    public abstract class Command<T>
        : ICommand, IDisposable
    {
        public ISessionContext Context { get; private set; }

        protected Command(ISessionContext context)
        {
            if(context==null) throw new ArgumentNullException("context");

            Context = context;
            _skipSecurityCheck = false;
        }

        protected Command(ISessionContext context, WarningSuppressions warningSupressions) 
            : this(context)
        {
            if (warningSupressions == null) throw new ArgumentNullException("warningSupressions");
            this.WarningSuppressions = warningSupressions;
        }

        protected WarningSuppressions WarningSuppressions { get; private set; }

        private bool _skipSecurityCheck;

        protected abstract void OnExecute(ExecuteResult<T> result);


        /// <summary>
        /// Should there be any execution failure the Undo command will
        /// be triggered thus allowing
        /// </summary>
        protected virtual void OnUndo()
        {
        }



        private bool CheckSecurity()
        {
            // Perform framework security checks



            // Allow custom security checks to be created by the 
            // implementer of the command.
            return OnCheckSecurity();
        }


        protected virtual bool OnCheckSecurity()
        {
            return true;
        }

        /// <summary>
        /// Validates the the command data allowing business rules
        /// to be checked.  
        /// </summary>
        /// <param name="result"></param>
        /// <returns>
        /// Any rule violation should cause either a warning or
        /// error to be added.  Should the error be fatal the method should
        /// return false.
        /// </returns>
        protected virtual bool OnValidate(ExecuteResult<T> result)
        {
            return result.Status != ExecuteResultStatus.Failure;
        }

        /// <summary>
        /// Allows any default data to be addeded or massaged prior
        /// to validation.  This could be used to prepare the data
        /// </summary>
        protected virtual void OnPreValidate()
        {
        }


        /// <summary>
        /// This fires just prior to the command being executed, thus
        /// allowing any final data manipulation prior to being executed.
        /// </summary>
        protected virtual void OnPrepare()
        {
        }

        /// <summary>
        /// This occurs once the command as successfully completed.  
        /// </summary>
        protected virtual void OnComplete()
        {
        }



        #region ICommand<T> Members


        public ExecuteResult<T> Execute(bool skipSecurityCheck)
        {
            _skipSecurityCheck = skipSecurityCheck;
            return Execute();
        }


        public ExecuteResult<T> Execute()
        {
            var er = new ExecuteResult<T>();

            try
            {
                // Check security to ensure the user is allowed to execute
                // the command
                if (!_skipSecurityCheck && !CheckSecurity())
                {
                    er.Status = ExecuteResultStatus.Failure;
                    return er;
                }

                // Prepare the command allowing the command implementer to
                // add any additional code to the command.  If an error is
                // thrown in the prepare it is the implementers fault and
                // the cammand will fail with an exception.
                OnPreValidate();


                //// Ensure the sesison has not expired
                //if (this.Context.Session.IsExpired())
                //{
                //    er.AddError(Globalisation.SDK.SECURITY_SESSION_EXPIRED);
                //    return er;
                //}

                //// Update the last activity time
                //this.Context.Session.UpdateLastActivity();



                // Validate the command will firstly use reflection to 
                // ensure all properties are correct and within design
                // time parameters.  This will check the parameters against
                // the validation attributes.  Once checked a custome
                // virtual method will be raised, allowing the implementer
                // to write further business logic.
                if (!OnValidate(er))
                {
                    er.Status = ExecuteResultStatus.Failure;
                    return er;
                }

                // Execute the command within a transaction and should the
                // command fail, attempt to undo the command automaticall.
                try
                {
                    InternalExecute(er);
                    OnComplete();
                }
                catch (Exception ex)
                {
                    // Set the state to failure and
                    // track the exception
                    er.AddException(ex);

                    // Attempt to undo the command
                    try
                    {
                        OnUndo();
                    }
                    catch (Exception exUndo)
                    {
                        // TODO:    An architectural decision needs to be made 
                        //          here regarding a failure in the Undo() call,
                        //          Do we gracefully handle it and add it to the
                        //          results of the command.  Or do we throw the
                        //          exception as Undo failed, i.e. is an Undo
                        //          failure deemed as catastropic?
                        er.AddException(exUndo);
                    }
                }
            }
            catch (Exception ex)
            {
                // Set the state to failure and
                // track the exception
                er.AddException(ex);
            }


            return er;
        }

        internal virtual void InternalExecute(ExecuteResult<T> er)
        {
            OnExecute(er);
        }

        #endregion

        #region ICommand Members

        ExecuteResult ICommand.Execute()
        {
            return this.Execute();
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {

        }

        #endregion
    }
}
